package ops.weixin.cp.api.impl;

import java.io.File;
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static ops.weixin.cp.constant.WxCpApiPathConsts.*;

import ops.weixin.common.WxType;
import ops.weixin.common.error.WxError;
import ops.weixin.common.error.WxErrorException;
import ops.weixin.common.util.DataUtils;
import ops.weixin.common.util.crypto.SHA1;
import ops.weixin.common.util.http.RequestExecutor;
import ops.weixin.common.util.http.RequestHttp;
import ops.weixin.common.util.http.SimpleGetRequestExecutor;
import ops.weixin.common.util.http.SimplePostRequestExecutor;
import ops.weixin.cp.api.WxCpDepartmentService;
import ops.weixin.cp.api.WxCpOAuth2Service;
import ops.weixin.cp.api.WxCpService;
import ops.weixin.cp.api.WxCpUserService;
import ops.weixin.cp.bean.WxCpMessage;
import ops.weixin.cp.bean.WxCpMessageSendResult;
import ops.weixin.cp.config.WxCpConfigStorage;

/**
 * 统一管理
 * 
 * @author SuM
 * @date: 2019年12月24日下午3:56:26
 */
public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestHttp<H, P> {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	private WxCpUserService userService = new WxCpUserServiceImpl(this);
	private WxCpDepartmentService departmentService = new WxCpDepartmentServiceImpl(this);
	private WxCpOAuth2Service oauth2Service = new WxCpOAuth2ServiceImpl(this);

	/**
	 * 全局的是否正在刷新access token的锁.
	 */
	protected final Object globalAccessTokenRefreshLock = new Object();

	protected WxCpConfigStorage configStorage;

	/**
	 * 临时文件目录.
	 */
	private File tmpDirFile;
	private int retrySleepMillis = 1000;
	private int maxRetryTimes = 5;

	@Override
	public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) {
		try {
			return SHA1.gen(this.configStorage.getToken(), timestamp, nonce, data).equals(msgSignature);
		} catch (Exception e) {
			logger.error("Checking signature failed, and the reason is :" + e.getMessage());
			return false;
		}
	}

	@Override
	public String getAccessToken() throws WxErrorException {
		return getAccessToken(false);
	}

	@Override
	public WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException {
		Integer agentId = message.getAgentId();
		if (null == agentId) {
			message.setAgentId(this.getWxCpConfigStorage().getAgentId());
		}

		return WxCpMessageSendResult.fromJson(this.post(this.configStorage.getApiUrl(MESSAGE_SEND), message.toJson()));
	}

	@Override
	public String get(String url, String queryParam) throws WxErrorException {
		return execute(SimpleGetRequestExecutor.create(this), url, queryParam);
	}

	@Override
	public String post(String url, String postData) throws WxErrorException {
		return execute(SimplePostRequestExecutor.create(this), url, postData);
	}

	/**
	 * 向微信端发送请求，在这里执行的策略是当发生access_token过期时才去刷新，然后重新执行请求，而不是全局定时请求.
	 */
	@Override
	public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
		int retryTimes = 0;
		do {
			try {
				return this.executeInternal(executor, uri, data);
			} catch (WxErrorException e) {
				if (retryTimes + 1 > this.maxRetryTimes) {
					logger.warn("重试达到最大次数【{}】", this.maxRetryTimes);
					// 最后一次重试失败后，直接抛出异常，不再等待
					throw new RuntimeException("微信服务端异常，超出重试次数");
				}

				WxError error = e.getError();
				/*
				 * -1 系统繁忙, 1000ms后重试
				 */
				if (error.getErrorCode() == -1) {
					int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
					try {
						logger.debug("微信系统繁忙，{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
						Thread.sleep(sleepMillis);
					} catch (InterruptedException e1) {
						Thread.currentThread().interrupt();
					}
				} else {
					throw e;
				}
			}
		} while (retryTimes++ < this.maxRetryTimes);

		logger.warn("重试达到最大次数【{}】", this.maxRetryTimes);
		throw new RuntimeException("微信服务端异常，超出重试次数");
	}

	protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
		E dataForLog = DataUtils.handleDataWithSecret(data);

		if (uri.contains("access_token=")) {
			throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
		}
		String accessToken = getAccessToken(false);

		String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken;

		try {
			T result = executor.execute(uriWithAccessToken, data, WxType.CP);
			// log.debug("\n【请求地址】: {}\n【请求参数】：{}\n【响应数据】：{}", uriWithAccessToken,
			// dataForLog, result);
			return result;
		} catch (WxErrorException e) {
			WxError error = e.getError();
			/*
			 * 发生以下情况时尝试刷新access_token 40001 获取access_token时AppSecret错误，或者access_token无效
			 * 42001 access_token超时 40014
			 * 不合法的access_token，请开发者认真比对access_token的有效性（如是否过期），或查看是否正在为恰当的公众号调用接口
			 */
			if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) {
				// 强制设置wxCpConfigStorage它的access token过期了，这样在下一次请求里就会刷新access token
				this.configStorage.expireAccessToken();
				return execute(executor, uri, data);
			}

			if (error.getErrorCode() != 0) {
				logger.error("\n【请求地址】: {}\n【请求参数】：{}\n【错误信息】：{}", uriWithAccessToken, dataForLog, error);
				throw new WxErrorException(error, e);
			}
			return null;
		} catch (IOException e) {
			logger.error("\n【请求地址】: {}\n【请求参数】：{}\n【异常信息】：{}", uriWithAccessToken, dataForLog, e.getMessage());
			throw new RuntimeException(e);
		}
	}

	@Override
	public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) {
		this.configStorage = wxConfigProvider;
		this.initHttp();
	}

	@Override
	public void setRetrySleepMillis(int retrySleepMillis) {
		this.retrySleepMillis = retrySleepMillis;
	}

	@Override
	public void setMaxRetryTimes(int maxRetryTimes) {
		this.maxRetryTimes = maxRetryTimes;
	}

	@Override
	public String getTaskResult(String joinId) throws WxErrorException {
		String url = this.configStorage.getApiUrl(BATCH_GET_RESULT + joinId);
		return get(url, null);
	}

	public File getTmpDirFile() {
		return this.tmpDirFile;
	}

	public void setTmpDirFile(File tmpDirFile) {
		this.tmpDirFile = tmpDirFile;
	}

	@Override
	public WxCpDepartmentService getDepartmentService() {
		return departmentService;
	}

	@Override
	public WxCpOAuth2Service getOauth2Service() {
		return oauth2Service;
	}

	@Override
	public WxCpUserService getUserService() {
		return userService;
	}

	@Override
	public RequestHttp<?, ?> getRequestHttp() {
		return this;
	}

	@Override
	public void setUserService(WxCpUserService userService) {
		this.userService = userService;
	}

	@Override
	public void setDepartmentService(WxCpDepartmentService departmentService) {
		this.departmentService = departmentService;
	}

	@Override
	public void setOauth2Service(WxCpOAuth2Service oauth2Service) {
		this.oauth2Service = oauth2Service;
	}
}
